package com.powerunion.datacollection.common;

import java.util.*;

/**
 * Fast, simple, and yet useful FormatUtiltings.
 */
public class FormatUtil {
  private int width;
  private int precision;
  private String pre;
  private String post;
  private boolean leadingZeroes;
  private boolean showPlus;
  private boolean alternate;
  private boolean showSpace;
  private boolean leftAlign;
  private char fmt; // one of cdeEfgGiosxXos
  private boolean countSignInLen;

  /**
   * FormatUtils a number in a printf FormatUtil, like C
   *
   * @param s      the FormatUtil string following printf FormatUtil string
   *               The string has a prefix, a FormatUtil code and a suffix. The prefix and suffix
   *               become part of the FormatUtilted output. The FormatUtil code directs the
   *               FormatUtilting of the (single) parameter to be FormatUtilted. The code has the
   *               following structure
   *               <ul>
   *               <li> a % (required)
   *               <li> a modifier (optional)
   *               <dl>
   *               <dt> + <dd> forces display of + for positive numbers
   *               <dt> ~ <dd> do not count leading + or - in length
   *               <dt> 0 <dd> show leading zeroes
   *               <dt> - <dd> align left in the field
   *               <dt> space <dd> prepend a space in front of positive numbers
   *               <dt> # <dd> use "alternate" FormatUtil. Add 0 or 0x for octal or hexadecimal numbers.
   *               Don't suppress trailing zeroes in general floating point FormatUtil.
   *               </dl>
   *               <li> an integer denoting field width (optional)
   *               <li> a period followed by an integer denoting precision (optional)
   *               <li> a FormatUtil descriptor (required)
   *               <dl>
   *               <dt>f <dd> floating point number in fixed FormatUtil
   *               <dt>e, E <dd> floating point number in exponential notation (scientific FormatUtil).
   *               The E FormatUtil results in an uppercase E for the exponent (1.14130E+003), the e
   *               FormatUtil in a lowercase e.
   *               <dt>g, G <dd> floating point number in general FormatUtil (fixed FormatUtil for small
   *               numbers, exponential FormatUtil for large numbers). Trailing zeroes are suppressed.
   *               The G FormatUtil results in an uppercase E for the exponent (if any), the g FormatUtil
   *               in a lowercase e.
   *               <dt>d, i <dd> signed long integer and integer in decimal
   *               <dt>u <dd> unsigned integer in decimal
   *               <dt>x <dd> unsigned integer in hexadecimal
   *               <dt>o <dd> unsigned integer in octal
   *               <dt>s <dd> string
   *               <dt>c <dd> character
   *               </dl>
   *               </ul>
   */
  public FormatUtil(String s) {
    width = 0;
    precision = -1;
    pre = "";
    post = "";
    leadingZeroes = false;
    showPlus = false;
    alternate = false;
    showSpace = false;
    leftAlign = false;
    countSignInLen = true;
    fmt = ' ';

    int state = 0;
    int length = s.length();
    int parseState = 0;

    // 0 = prefix, 1 = flags, 2 = width, 3 = precision,
    // 4 = FormatUtil, 5 = end
    int i = 0;

    while (parseState == 0) {
      if (i >= length) {
        parseState = 5;
      }
      else if (s.charAt(i) == '%') {
        if (i < length - 1) {
          if (s.charAt(i + 1) == '%') {
            pre = pre + '%';
            i++;
          }
          else {
            parseState = 1;
          }
        }
        else {
          throw new java.lang.IllegalArgumentException();
        }
      }
      else {
        pre = pre + s.charAt(i);
      }
      i++;
    }

    while (parseState == 1) {
      if (i >= length) {
        parseState = 5;
      }
      else if (s.charAt(i) == ' ') {
        showSpace = true;
      }
      else if (s.charAt(i) == '-') {
        leftAlign = true;
      }
      else if (s.charAt(i) == '+') {
        showPlus = true;
      }
      else if (s.charAt(i) == '0') {
        leadingZeroes = true;
      }
      else if (s.charAt(i) == '#') {
        alternate = true;
      }
      else if (s.charAt(i) == '~') {
        countSignInLen = false;
      }
      else {
        parseState = 2;
        i--;
      }
      i++;
    }

    while (parseState == 2) {
      if (i >= length) {
        parseState = 5;
      }
      else if ('0' <= s.charAt(i) && s.charAt(i) <= '9') {
        width = width * 10 + s.charAt(i) - '0';
        i++;
      }
      else if (s.charAt(i) == '.') {
        parseState = 3;
        precision = 0;
        i++;
      }
      else {
        parseState = 4;
      }
    }

    while (parseState == 3) {
      if (i >= length) {
        parseState = 5;
      }
      else if ('0' <= s.charAt(i) && s.charAt(i) <= '9') {
        precision = precision * 10 + s.charAt(i) - '0';
        i++;
      }
      else {
        parseState = 4;
      }
    }

    if (parseState == 4) {
      if (i >= length) {
        parseState = 5;
      }
      else {
        fmt = s.charAt(i);
      }

      i++;
    }
    if (i < length) {
      post = s.substring(i, length);
    }
  }

  /**
   *
   * @param d
   *
   * @return
   */
  private String expFormatUtil(double d) {
    String f = "";
    int e = 0;
    double dd = d;
    double factor = 1;

    if (d != 0) {
      while (dd > 10) {
        e++;
        factor /= 10;
        dd = dd / 10;
      }
      while (dd < 1) {
        e--;
        factor *= 10;
        dd = dd * 10;
      }
    }
    if ( (fmt == 'g' || fmt == 'G') && e >= -4 && e < precision) {
      return fixedFormatUtil(d);
    }

    d = d * factor;
    f = f + fixedFormatUtil(d);

    if (fmt == 'e' || fmt == 'g') {
      f = f + "e";
    }
    else {
      f = f + "E";
    }

    String p = "000";

    if (e >= 0) {
      f = f + "+";
      p = p + e;
    }
    else {
      f = f + "-";
      p = p + ( -e);
    }

    return f + p.substring(p.length() - 3, p.length());
  }

  /**
   *
   * @param d
   *
   * @return
   */
  private String fixedFormatUtil(double d) {
    boolean removeTrailing = (fmt == 'G' || fmt == 'g') && !alternate;

    // remove trailing zeroes and decimal point
    if (d > 0x7FFFFFFFFFFFFFFFL) {
      return expFormatUtil(d);
    }
    if (precision == 0) {
      return (long) (d /*+ 0.5*/) + (removeTrailing ? "" : "."); // no rounding
    }

    long whole = (long) d;
    double fr = d - whole; // fractional part

    if (fr >= 1 || fr < 0) {
      return expFormatUtil(d);
    }

    double factor = 1;
    String leadingZeroes = "";

    for (int i = 1; i <= precision && factor <= 0x7FFFFFFFFFFFFFFFL; i++) {
      factor *= 10;
      leadingZeroes = leadingZeroes + "0";
    }

    long l = (long) (factor * fr /*+ 0.5*/); // no rounding

    if (l >= factor) {
      l = 0;
      whole++;
    }

    String z = leadingZeroes + l;
    z = "." + z.substring(z.length() - precision, z.length());

    if (removeTrailing) {
      int t = z.length() - 1;

      while (t >= 0 && z.charAt(t) == '0') {
        t--;
      }
      if (t >= 0 && z.charAt(t) == '.') {
        t--;
      }

      z = z.substring(0, t + 1);
    }

    return whole + z;
  }

  /**
   *
   * @param r
   *
   * @return
   */
  private String pad(String r) {
    String p = repeat(' ', width - r.length());

    if (leftAlign) {
      return pre + r + p + post;
    }
    else {
      return pre + p + r + post;
    }
  }

  /**
   *
   * @param s
   * @param base
   *
   * @return
   */
  private static long parseLong(String s, int base) {
    int i = 0;
    int sign = 1;
    long r = 0;

    while (i < s.length() && Character.isWhitespace(s.charAt(i))) {
      i++;
    }
    if (i < s.length() && s.charAt(i) == '-') {
      sign = -1;
      i++;
    }
    else if (i < s.length() && s.charAt(i) == '+') {
      i++;
    }
    while (i < s.length()) {
      char ch = s.charAt(i);

      if ('0' <= ch && ch < '0' + base) {
        r = r * base + ch - '0';
      }
      else if ('A' <= ch && ch < 'A' + base - 10) {
        r = r * base + ch - 'A' + 10;
      }
      else if ('a' <= ch && ch < 'a' + base - 10) {
        r = r * base + ch - 'a' + 10;
      }
      else {
        return r * sign;
      }

      i++;
    }

    return r * sign;
  }

  /**
   *
   * @param c
   * @param n
   *
   * @return
   */
  private static String repeat(char c, int n) {
    if (n <= 0) {
      return ("");
    }

    StringBuffer s = new StringBuffer(n);

    for (int i = 0; i < n; i++) {
      s.append(c);
    }

    return s.toString();
  }

  /**
   *
   * @param s
   * @param r
   *
   * @return
   */
  private String sign(int s, String r) {
    String p = "";

    if (s < 0) {
      p = "-";
    }
    else if (s > 0) {
      if (showPlus) {
        p = "+";
      }
      else if (showSpace) {
        p = " ";
      }
    }
    else {
      if (fmt == 'o' && alternate && r.length() > 0 && r.charAt(0) != '0') {
        p = "0";
      }
      else if (fmt == 'x' && alternate) {
        p = "0x";
      }
      else if (fmt == 'X' && alternate) {
        p = "0X";
      }
    }

    int w = 0;

    if (leadingZeroes) {
      w = width;
    }
    else if ( (fmt == 'u' || fmt == 'd' || fmt == 'i' || fmt == 'x' ||
               fmt == 'X' || fmt == 'o') && precision > 0) {
      w = precision;
    }

    if (countSignInLen) {
      return p + repeat('0', w - p.length() - r.length()) + r;
    }
    else {
      return p + repeat('0', w - r.length()) + r;
    }
  }

  // ---------------------------------------------------------------- form methods

  /**
   * FormatUtils a character into a string (like sprintf in C)
   *
   * @param c      the value to FormatUtil
   *
   * @return the FormatUtilted string
   */
  public String form(char c) {
    if (fmt != 'c') {
      throw new java.lang.IllegalArgumentException();
    }

    String r = "" + c;

    return pad(r);
  }

  public String form(Character c) {
    return form(c.charValue());
  }

  /**
   * FormatUtils a double into a string (like sprintf in C)
   *
   * @param x      the number to FormatUtil
   *
   * @return the FormatUtilted string
   */
  public String form(double x) {
    String r;

    if (precision < 0) {
      precision = 6;
    }

    int s = 1;

    if (x < 0) {
      x = -x;
      s = -1;
    }
    if (fmt == 'f') {
      r = fixedFormatUtil(x);
    }
    else if (fmt == 'e' || fmt == 'E' || fmt == 'g' || fmt == 'G') {
      r = expFormatUtil(x);
    }
    else {
      throw new java.lang.IllegalArgumentException();
    }

    return pad(sign(s, r));
  }

  public String form(Double x) {
    return form(x.doubleValue());
  }

  public String form(Float x) {
    return form(x.doubleValue());
  }

  /**
   * FormatUtils a long integer into a string (like sprintf in C)
   *
   * @param x      the number to FormatUtil
   *
   * @return the FormatUtilted string
   */
  public String form(long x) {
    String r;
    int s = 0;

    if (fmt == 'd') {
      if (x < 0) {
        r = ("" + x).substring(1);
        s = -1;
      }
      else {
        r = "" + x;
        s = 1;
      }
    }
    else if (fmt == 'i') {
      int xx = (int) x;
      if (xx < 0) {
        r = ("" + xx).substring(1);
        s = -1;
      }
      else {
        r = "" + xx;
        s = 1;
      }
    }
    else if (fmt == 'u') {
      long xl = x & 0x00000000FFFFFFFFL;
      r = "" + xl;
      s = 1;
    }
    else if (fmt == 'o') {
      r = convert(x, 3, "01234567");
    }
    else if (fmt == 'x') {
      r = convert(x, 4, "0123456789abcdef");
    }
    else if (fmt == 'X') {
      r = convert(x, 4, "0123456789ABCDEF");
    }
    else {
      throw new java.lang.IllegalArgumentException();
    }

    return pad(sign(s, r));
  }

  public String form(Long x) {
    return form(x.longValue());
  }

  /**
   * FormatUtils an integer into a string (like sprintf in C)
   *
   * @param x      the number to FormatUtil
   *
   * @return the FormatUtilted string
   */
  public String form(int x) {
    String r;
    int s = 0;

    if (fmt == 'd' || fmt == 'i') {
      if (x < 0) {
        r = ("" + x).substring(1);
        s = -1;
      }
      else {
        r = "" + x;
        s = 1;
      }
    }
    else {
      long xl = x & 0x00000000FFFFFFFFL;
      if (fmt == 'u') {
        r = "" + xl;
        s = 1;
      }
      else if (fmt == 'o') {
        r = convert(xl, 3, "01234567");
      }
      else if (fmt == 'x') {
        r = convert(xl, 4, "0123456789abcdef");
      }
      else if (fmt == 'X') {
        r = convert(xl, 4, "0123456789ABCDEF");
      }
      else {
        throw new java.lang.IllegalArgumentException();
      }
    }

    return pad(sign(s, r));
  }

  public String form(Integer x) {
    return form(x.intValue());
  }

  /**
   * FormatUtils a string into a larger string (like sprintf in C)
   *
   * @param s      the value to FormatUtil
   *
   * @return the FormatUtilted string
   */
  public String form(String s) {
    if (fmt != 's') {
      throw new java.lang.IllegalArgumentException();
    }
    if (precision >= 0 && precision < s.length()) {
      s = s.substring(0, precision);
    }

    return pad(s);
  }

  // ---------------------------------------------------------------- misc conversion

  /**
   * Converts a string of digits to an double
   *
   * @param s      a string
   *
   * @return double converted from String
   */
  public static double atof(String s) {
    int i = 0;
    int sign = 1;
    double r = 0; // integer part
    double f = 0; // fractional part
    double p = 1; // exponent of fractional part
    int state = 0; // 0 = int part, 1 = frac part

    while (i < s.length() && Character.isWhitespace(s.charAt(i))) {
      i++;
    }
    if (i < s.length() && s.charAt(i) == '-') {
      sign = -1;
      i++;
    }
    else if (i < s.length() && s.charAt(i) == '+') {
      i++;
    }
    while (i < s.length()) {
      char ch = s.charAt(i);

      if ('0' <= ch && ch <= '9') {
        if (state == 0) {
          r = r * 10 + ch - '0';
        }
        else if (state == 1) {
          p = p / 10;
          r = r + p * (ch - '0');
        }
      }
      else if (ch == '.') {
        if (state == 0) {
          state = 1;
        }
        else {
          return sign * r;
        }
      }
      else if (ch == 'e' || ch == 'E') {
        long e = (int) parseLong(s.substring(i + 1), 10);

        return sign * r * Math.pow(10, e);
      }
      else {
        return sign * r;
      }
      i++;
    }
    return sign * r;
  }

  /**
   * Converts a string of digits (decimal, octal or hex) to an integer
   *
   * @param s      a string
   *
   * @return the numeric value of the prefix of s representing a
   *         base 10 integer
   */
  public static int atoi(String s) {
    return (int) atol(s);
  }

  /**
   * Converts a string of digits (decimal, octal or hex) to a long integer
   *
   * @param s      a string
   *
   * @return the numeric value of the prefix of s representing a
   *         base 10 integer
   */
  public static long atol(String s) {
    int i = 0;

    while (i < s.length() && Character.isWhitespace(s.charAt(i))) {
      i++;
    }
    if (i < s.length() && s.charAt(i) == '0') {
      if (i + 1 < s.length() &&
          (s.charAt(i + 1) == 'x' || s.charAt(i + 1) == 'X')) {
        return parseLong(s.substring(i + 2), 16);
      }
      else {
        return parseLong(s, 8);
      }
    }
    else {
      return parseLong(s, 10);
    }
  }

  /**
   * Converts number to string
   *
   * @param x      value to convert
   * @param n      conversion base
   * @param d      string with characters for conversion.
   *
   * @return converted number as string
   */
  public static String convert(long x, int n, String d) {
    if (x == 0) {
      return "0";
    }

    String r = "";
    int m = 1 << n;
    m--;
    while (x != 0) {
      r = d.charAt( (int) (x & m)) + r;
      x = x >>> n;
    }
    return r;
  }

  // ---------------------------------------------------------------- sprintf

  /**
   * prints a FormatUtilted number following printf conventions
   *
   * @param fmt    the FormatUtil string
   * @param x      the character to
   *
   * @return FormatUtiled string
   */
  public static String sprintf(String fmt, char x) {
    return new FormatUtil(fmt).form(x);
  }

  public static String sprintf(String fmt, Character x) {
    return new FormatUtil(fmt).form(x);
  }

  /**
   * prints a FormatUtilted number following printf conventions
   *
   * @param fmt    the FormatUtil string
   * @param x      the double to print
   *
   * @return FormatUtiled string
   */
  public static String sprintf(String fmt, double x) {
    return new FormatUtil(fmt).form(x);
  }

  public static String sprintf(String fmt, Double x) {
    return new FormatUtil(fmt).form(x);
  }

  public static String sprintf(String fmt, Float x) {
    return new FormatUtil(fmt).form(x);
  }

  /**
   * prints a FormatUtilted number following printf conventions
   *
   * @param fmt    the FormatUtil string
   * @param x      the long to print
   *
   * @return FormatUtiled string
   */
  public static String sprintf(String fmt, long x) {
    return new FormatUtil(fmt).form(x);
  }

  public static String sprintf(String fmt, Long x) {
    return new FormatUtil(fmt).form(x);
  }

  /**
   * prints a FormatUtilted number following printf conventions
   *
   * @param fmt    the FormatUtil string
   * @param x      the int to print
   *
   * @return FormatUtiled string
   */
  public static String sprintf(String fmt, int x) {
    return new FormatUtil(fmt).form(x);
  }

  public static String sprintf(String fmt, Integer x) {
    return new FormatUtil(fmt).form(x);
  }

  /**
   * prints a FormatUtilted number following printf conventions
   *
   * @param fmt
   * @param x      a string that represents the digits to print
   */
  public static String sprintf(String fmt, String x) {
    return new FormatUtil(fmt).form(x);
  }

  // ---------------------------------------------------------------- array sprintf

  /**
   * Splits input string on '%' and returns an array of substrings that can be
   * used for multiple scanf. Each substring has a one-char prefix that can
   * take one of two values:
   * '+' wich indicates that substring can be simply added to the resulting string
   * ' ' wich indicates that substring has to be additionally processed.
   *
   * @param s      Input string
   *
   * @return splited input string
   */
  private static String[] split(String s) {
    ArrayList list = new ArrayList();

    int lasti = 0;
    int i = s.indexOf("%");
    if (i == -1) {
      return new String[] {
          s};
    }
    if (i > 0) {
      list.add("+" + s.substring(0, i));
      lasti = i;
      i++;
      i = s.indexOf("%", i);
    }
    else if (i == 0) {
      i = s.indexOf("%", i + 1);
    }
    while (i != -1) {
      String ss = s.substring(lasti, i);
      if (ss.equals("%")) {
        lasti = i;
        i++;
        i = s.indexOf("%", i);
        if (i != -1) {
          ss = s.substring(lasti, i);
        }
        else {
          ss = s.substring(lasti);
        }
        list.add("+" + ss);
      }
      else {
        list.add(" " + ss);
      }
      if (i == -1) {
        lasti = i;
        break;
      }
      lasti = i;
      i++;
      i = s.indexOf("%", i);
    }
    if (lasti != -1) {
      list.add(" " + s.substring(lasti));
    }

    String[] ret = new String[list.size()];
    for (i = 0; i < list.size(); i++) {
      ret[i] = (String) list.get(i);
    }
    return ret;
  }

  /**
   * Sprintf multiple strings.
   *
   * @param s
   * @param params
   *
   * @return FormatUtiled string
   */
  public static String sprintf(String s, Object[] params) {
    if ( (s == null) || (params == null)) {
      return s;
    }
    StringBuffer result = new StringBuffer("");
    String[] ss = split(s);
    int p = 0;
    for (int i = 0; i < ss.length; i++) {
      char c = ss[i].charAt(0);
      String t = ss[i].substring(1);
      if (c == '+') {
        result.append(t);
      }
      else {
        Object param = params[p];
        if (param instanceof Integer) {
          result.append(new FormatUtil(t).form( (Integer) param));
        }
        else if (param instanceof Long) {
          result.append(new FormatUtil(t).form( (Long) param));
        }
        else if (param instanceof Character) {
          result.append(new FormatUtil(t).form( (Character) param));
        }
        else if (param instanceof Double) {
          result.append(new FormatUtil(t).form( (Double) param));
        }
        else if (param instanceof Double) {
          result.append(new FormatUtil(t).form( (Float) param));
        }
        else {
          result.append(new FormatUtil(t).form(param.toString()));
        }
        p++;
      }
    }
    return result.toString();
  }

  public static String sprintf(String s, String[] params) {
    return sprintf(s, (Object[]) params);
  }

  public static String sprintf(String s, Integer[] params) {
    return sprintf(s, (Object[]) params);
  }

  public static String sprintf(String s, Long[] params) {
    return sprintf(s, (Object[]) params);
  }

  public static String sprintf(String s, Double[] params) {
    return sprintf(s, (Object[]) params);
  }

  public static String sprintf(String s, Float[] params) {
    return sprintf(s, (Object[]) params);
  }

  public static String sprintf(String s, Character[] params) {
    return sprintf(s, (Object[]) params);
  }

  private static char[] hexDigits = {
      '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
      'F'};

  /**
   * Returns a hex string representation of a 8 bit integer.
   * Very fast. Returned hex values are in uppercase.
   *
   * @param b      byte to convert to hex string
   *
   * @return hex value
   */
  public static String toHexString(int b) {
    char[] digits = new char[2];
    b = b & 255;

    digits[0] = hexDigits[b / 0x10];
    digits[1] = hexDigits[b % 0x10];

    return new String(digits);
  }

  // ---------------------------------------------------------------- primitives array sprintf

  public static String sprintf(String s, int[] params) {
    if ( (s == null) || (params == null)) {
      return s;
    }
    StringBuffer result = new StringBuffer("");
    String[] ss = split(s);
    int p = 0;
    for (int i = 0; i < ss.length; i++) {
      char c = ss[i].charAt(0);
      String t = ss[i].substring(1);
      if (c == '+') {
        result.append(t);
      }
      else {
        int param = params[p];
        result.append(new FormatUtil(t).form(param));
        p++;
      }
    }
    return result.toString();
  }
}
